﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Xml;
using System.Xml.Linq;
using FortuneCookie.EPiServer.SearchAndReplace.Configuration;

namespace FortuneCookie.EPiServer.SearchAndReplace.Data
{
    /// <summary>
    /// XmlDataHelper class
    /// </summary>
    internal sealed class XmlDataHelper : IDataHelper
    {
        
        #region Members

        private static readonly object Sync = new object();

        private const string CACHE_KEY = "SearchAndReplaceData";

        #endregion Members

        #region Properties

        /// <summary>
        /// Gets the xml file path
        /// </summary>
        private string FilePath
        {
            get
            {
                string filePath = ConfigurationHelper.XmlFilePath;
                return ConfigurationHelper.XmlFilePathType == XmlFilePathType.Absolute ? HttpRuntime.AppDomainAppPath + filePath.Replace("~/", string.Empty).Replace("/", @"\") : filePath;
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Gets all search and replace entities
        /// </summary>
        /// <returns></returns>
        public List<SearchAndReplace> GetAll()
        {
            List<SearchAndReplace> searchAndReplaces = GetFromCache();

            if (searchAndReplaces != null)
                return searchAndReplaces;

            StringReader reader = new StringReader(GetSearchAndReplaceXml());
            XDocument doc = XDocument.Load(reader);
            CultureInfo cultureInfo = new CultureInfo("en-GB");

            var query =
                from searchAndReplace in doc.Descendants("searchAndReplaces").First().Descendants("searchAndReplace")
                orderby DateTime.ParseExact(searchAndReplace.Element("created").Value.Trim(), "dd/MM/yyyy HH:mm:ss", cultureInfo) descending
                select new SearchAndReplace
                           {
                               Id = new Guid(searchAndReplace.Attribute("id").Value.Trim()),
                               StartPage = searchAndReplace.Element("startPage").Value.Trim(),
                               Created = DateTime.ParseExact(searchAndReplace.Element("created").Value.Trim(), "dd/MM/yyyy HH:mm:ss", cultureInfo),
                               CreatedBy = searchAndReplace.Element("createdBy").Value.Trim(),
                               Find = searchAndReplace.Element("find").Value.Trim(),
                               Replace = searchAndReplace.Element("replace").Value.Trim(),
                               MatchCase = bool.Parse(searchAndReplace.Element("matchCase").Value.Trim()),
                               UpdateDynamicProperties = bool.Parse(searchAndReplace.Element("updateDynamicProperties").Value.Trim()),
                               SearchWithinWords = bool.Parse(searchAndReplace.Element("searchWithinWords").Value.Trim()),
                               StartPageOnly = bool.Parse(searchAndReplace.Element("startPageOnly").Value.Trim()),
                               LanguageBranch = searchAndReplace.Element("languageBranch").Value.Trim(),
                               Completed = bool.Parse(searchAndReplace.Element("completed").Value.Trim()),
                               Existing = true,
                               HasChanges = false,
                               Pages = (from page in searchAndReplace.Descendants("pages").First().Descendants("page")
                                        orderby int.Parse(page.Attribute("sortIndex").Value.Trim()) ascending 
                                        select new Page
                                                   {
                                                       Id = new Guid(page.Attribute("id").Value.Trim()),
                                                       PageId = int.Parse(page.Attribute("pageId").Value.Trim()),
                                                       WorkId = int.Parse(page.Attribute("workId").Value.Trim()),
                                                       PreviousWorkId = int.Parse(page.Attribute("previousWorkId").Value.Trim()),
                                                       LanguageBranch = page.Attribute("languageBranch").Value.Trim(),
                                                       SortIndex = int.Parse(page.Attribute("sortIndex").Value.Trim()),
                                                       Existing = true,
                                                       HasChanges = false
                                                   }).ToList(),
                               DynamicProperties = (from dynamicProperty in searchAndReplace.Descendants("dynamicProperties").First().Descendants("dynamicProperty")
                                                    orderby int.Parse(dynamicProperty.Attribute("sortIndex").Value.Trim()) ascending
                                                    select new DynamicProperty
                                                               {
                                                                   Id = new Guid(dynamicProperty.Attribute("id").Value.Trim()),
                                                                   PageId = int.Parse(dynamicProperty.Attribute("pageId").Value.Trim()),
                                                                   WorkId = int.Parse(dynamicProperty.Attribute("workId").Value.Trim()),
                                                                   LanguageBranch = dynamicProperty.Attribute("languageBranch").Value.Trim(),
                                                                   PropertyName = dynamicProperty.Attribute("propertyName").Value,
                                                                   SortIndex = int.Parse(dynamicProperty.Attribute("sortIndex").Value.Trim()),
                                                                   Existing = true,
                                                                   HasChanges = false
                                                               }).ToList(),
                               Errors = (from error in searchAndReplace.Descendants("errors").First().Descendants("error")
                                         orderby int.Parse(error.Attribute("sortIndex").Value.Trim()) ascending
                                         select new Error
                                         {
                                             Id = new Guid(error.Attribute("id").Value.Trim()),
                                             PageId = int.Parse(error.Attribute("pageId").Value.Trim()),
                                             WorkId = int.Parse(error.Attribute("workId").Value.Trim()),
                                             LanguageBranch = error.Attribute("languageBranch").Value.Trim(),
                                             PropertyName = error.Attribute("propertyName").Value,
                                             ErrorMessage = error.Attribute("errorMessage").Value,
                                             PageProperty = bool.Parse(error.Attribute("propertyName").Value),
                                             SortIndex = int.Parse(error.Attribute("sortIndex").Value.Trim()),
                                             Existing = true,
                                             HasChanges = false
                                         }).ToList()
                           };

            reader.Dispose();

            searchAndReplaces = query.ToList();
            AddToCache(searchAndReplaces);
            return searchAndReplaces;
        }

        /// <summary>
        /// Gets a search and replace entity by it's id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public SearchAndReplace GetById(Guid id)
        {
            List<SearchAndReplace> searchAndReplaces = GetAll();
            return searchAndReplaces.Find(current => current.Id == id);
        }

        /// <summary>
        /// Updates a search and replace entity
        /// </summary>
        /// <param name="searchAndReplace"></param>
        public void Update(SearchAndReplace searchAndReplace)
        {
            List<SearchAndReplace> searchAndReplaces = GetAll();
            SearchAndReplace instance = searchAndReplaces.Find(current => current.Id == searchAndReplace.Id);

            if (instance == null)
                searchAndReplaces.Add(searchAndReplace);
            else
            {
                int index = searchAndReplaces.IndexOf(instance);
                searchAndReplaces[index] = searchAndReplace;
            }

            WriteToDisk(searchAndReplaces);
        }

        /// <summary>
        /// Deletes a search and replace entity
        /// </summary>
        /// <param name="searchAndReplace"></param>
        public void Delete(SearchAndReplace searchAndReplace)
        {
            List<SearchAndReplace> searchAndReplaces = GetAll();
            SearchAndReplace instance = searchAndReplaces.Find(current => current.Id == searchAndReplace.Id);

            if (instance != null)
                searchAndReplaces.Remove(instance);

            WriteToDisk(searchAndReplaces);
        }

        /// <summary>
        /// Writes the search and replace entitites to disk
        /// </summary>
        /// <param name="searchAndReplaces"></param>
        private void WriteToDisk(List<SearchAndReplace> searchAndReplaces)
        {
            StringBuilder stringBuilder = new StringBuilder();

            using (StringWriter stringWriter = new StringWriter(stringBuilder))
            {
                using (XmlTextWriter textWriter = new XmlTextWriter(stringWriter))
                {
                    textWriter.Formatting = Formatting.Indented;
                    textWriter.WriteStartElement("searchAndReplaces");

                    foreach (SearchAndReplace findAndReplace in searchAndReplaces)
                    {
                        textWriter.WriteStartElement("searchAndReplace");
                        textWriter.WriteAttributeString("id", findAndReplace.Id.ToString());
                        textWriter.WriteElementString("startPage", findAndReplace.StartPage);
                        textWriter.WriteElementString("created", findAndReplace.Created.ToString("dd/MM/yyyy HH:mm:ss"));
                        textWriter.WriteElementString("createdBy", findAndReplace.CreatedBy);
                        textWriter.WriteElementString("find", findAndReplace.Find);
                        textWriter.WriteElementString("replace", findAndReplace.Replace);
                        textWriter.WriteElementString("matchCase", findAndReplace.MatchCase.ToString());
                        textWriter.WriteElementString("searchWithinWords", findAndReplace.SearchWithinWords.ToString());
                        textWriter.WriteElementString("updateDynamicProperties", findAndReplace.UpdateDynamicProperties.ToString());
                        textWriter.WriteElementString("startPageOnly", findAndReplace.StartPageOnly.ToString());
                        textWriter.WriteElementString("languageBranch", findAndReplace.LanguageBranch);
                        textWriter.WriteElementString("completed", findAndReplace.Completed.ToString());
                        textWriter.WriteStartElement("pages");

                        foreach (Page page in findAndReplace.Pages)
                        {
                            textWriter.WriteStartElement("page");
                            textWriter.WriteAttributeString("id", page.Id.ToString());
                            textWriter.WriteAttributeString("pageId", page.PageId.ToString());
                            textWriter.WriteAttributeString("workId", page.WorkId.ToString());
                            textWriter.WriteAttributeString("previousWorkId", page.PreviousWorkId.ToString());
                            textWriter.WriteAttributeString("languageBranch", page.LanguageBranch);
                            textWriter.WriteAttributeString("sortIndex", page.SortIndex.ToString());
                            textWriter.WriteEndElement();
                        }

                        textWriter.WriteEndElement();

                        textWriter.WriteStartElement("dynamicProperties");

                        foreach (DynamicProperty dynamicProperty in findAndReplace.DynamicProperties)
                        {
                            textWriter.WriteStartElement("dynamicProperty");
                            textWriter.WriteAttributeString("id", dynamicProperty.Id.ToString());
                            textWriter.WriteAttributeString("pageId", dynamicProperty.PageId.ToString());
                            textWriter.WriteAttributeString("workId", dynamicProperty.WorkId.ToString());
                            textWriter.WriteAttributeString("languageBranch", dynamicProperty.LanguageBranch);
                            textWriter.WriteAttributeString("propertyName", dynamicProperty.PropertyName);
                            textWriter.WriteAttributeString("sortIndex", dynamicProperty.SortIndex.ToString());
                            textWriter.WriteEndElement();
                        }

                        textWriter.WriteEndElement();

                        textWriter.WriteStartElement("errors");

                        foreach (Error error in findAndReplace.Errors)
                        {
                            textWriter.WriteStartElement("error");
                            textWriter.WriteAttributeString("id", error.Id.ToString());
                            textWriter.WriteAttributeString("pageId", error.PageId.ToString());
                            textWriter.WriteAttributeString("workId", error.WorkId.ToString());
                            textWriter.WriteAttributeString("languageBranch", error.LanguageBranch);
                            textWriter.WriteAttributeString("propertyName", error.PropertyName);
                            textWriter.WriteAttributeString("errorMessage", error.ErrorMessage);
                            textWriter.WriteAttributeString("pageProperty", error.PageProperty.ToString());
                            textWriter.WriteAttributeString("sortIndex", error.SortIndex.ToString());
                            textWriter.WriteEndElement();
                        }

                        textWriter.WriteEndElement();

                        textWriter.WriteEndElement();
                    }

                    textWriter.WriteEndElement();
                }
            }

            lock (Sync)
            {
                File.WriteAllText(FilePath, stringBuilder.ToString());
                RemoveFromCache();
            }
        }

        /// <summary>
        /// Gets the search and replace xml
        /// </summary>
        /// <returns></returns>
        private string GetSearchAndReplaceXml()
        {
            return File.ReadAllText(FilePath);
        }

        /// <summary>
        /// Add the search and replace entities to cache
        /// </summary>
        /// <param name="searchAndReplaces"></param>
        private void AddToCache(List<SearchAndReplace> searchAndReplaces)
        {
            CacheDependency cacheDependency = new CacheDependency(FilePath);
            HttpRuntime.Cache.Insert(CACHE_KEY, searchAndReplaces, cacheDependency,
                DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
        }

        /// <summary>
        /// Gets the search and replace entities from cache
        /// </summary>
        /// <returns></returns>
        private List<SearchAndReplace> GetFromCache()
        {
            return HttpRuntime.Cache[CACHE_KEY] as List<SearchAndReplace>;
        }

        /// <summary>
        /// Removes the search and replace entities from cache
        /// </summary>
        private void RemoveFromCache()
        {
            HttpRuntime.Cache.Remove(CACHE_KEY);
        }

        #endregion Methods

    }
}
